﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Threading;
using System.Runtime.InteropServices;
using System.Diagnostics;

namespace SnapLib
{
   public class ForeignWindow
   {
      internal IntPtr m_hwnd;
      private bool m_bAltDown = false;
      private uint m_ProcessId = 0;
      private IntPtr m_hProcess = IntPtr.Zero;

      public ForeignWindow(IntPtr hwnd)
      {
         m_hwnd = hwnd;
      }

      #region internals
      public IntPtr Handle
      {
         get
         {
            return m_hwnd;
         }
      }

      public static implicit operator IntPtr(ForeignWindow from)
      {
         return from.m_hwnd;
      }

      public DC GetDC()
      {
         return new DC(this);
      }
      #endregion

      #region control

      public void Move(int x, int y)
      {
         RECT rect;
         Win32.GetWindowRect(m_hwnd, out rect);
         Win32.MoveWindow(m_hwnd, x, y, rect.Width, rect.Height, true);
      }
      public void Close()
      {
         Win32.PostMessage(m_hwnd, WindowsMessage.WM_CLOSE, 0, 0);
      }

      public void Destroy()
      {
         Win32.PostMessage(m_hwnd, WindowsMessage.WM_DESTROY, 0, 0);
      }

      public void Focus()
      {
         ForeignWindow TopMostWnd = MainWindow;
         Win32.SetForegroundWindow(TopMostWnd.m_hwnd);
         Win32.PostMessage(m_hwnd, WindowsMessage.WM_MOUSEACTIVATE, (uint)TopMostWnd.m_hwnd, 0x2010001);
         Win32.PostMessage(m_hwnd, WindowsMessage.WM_LBUTTONDOWN, 1, 0);
         Win32.PostMessage(m_hwnd, WindowsMessage.WM_LBUTTONUP, 0, 0);
      }

      public bool TopMost
      {
         get
         {
            return false;
         }
         set
         {
            if (value)
               Win32.SetWindowPos(m_hwnd, (IntPtr)(-1), 0, 0, 0, 0, SetWindowPosFlags.SWP_NOMOVE | SetWindowPosFlags.SWP_NOSIZE);
            else
               Win32.SetWindowPos(m_hwnd, (IntPtr)(-2), 0, 0, 0, 0, SetWindowPosFlags.SWP_NOMOVE | SetWindowPosFlags.SWP_NOSIZE);
         }
      }

      public void Activate()
      {
         Win32.SetActiveWindow(this.m_hwnd);
         Win32.SetForegroundWindow(this.m_hwnd);
      }

      public void BringToFront()
      {
         Win32.BringWindowToTop(this.m_hwnd);
      }

      public void Show(WindowShowStyle val)
      {
         Win32.ShowWindow(m_hwnd, val);
      }

      public WindowShowStyle WindowState
      {
         get
         {
            WINDOWPLACEMENT wp;
            Win32.GetWindowPlacement(m_hwnd, out wp);
            return (WindowShowStyle)wp.ShowCmd;
         }
      }

      [Obsolete]
      public void ShowWindow(WindowShowStyle val)
      {
         Win32.ShowWindow(m_hwnd, val);
      }
      #endregion

      #region standard info
      public Size Size
      {
         get
         {
            return WindowRect.Size;
         }
         set
         {
            Point pos = WindowRect.Location;
            Win32.MoveWindow(m_hwnd, pos.X, pos.Y, value.Width, value.Height, true);
         }
      }

      public Point Location
      {
         get
         {
            return WindowRect.Location;
         }
         set
         {
            Move(value.X, value.Y);
         }
      }

      public Size ClientSize
      {
         get
         {
            return ClientArea.Size;
         }
      }

      public int Width
      {
         get
         {
            return Size.Width;
         }
      }
      public int Height
      {
         get
         {
            return Size.Height;
         }
      }

      public ForeignWindow FindChild(string caption, string className)
      {
         foreach (ForeignWindow wnd in Children)
         {
            if ((wnd.Caption == caption || caption == null) &&
                (wnd.ClassName == className || className == null))
               return wnd;
         }
         return null;
      }

      ~ForeignWindow()
      {
         if (m_hProcess != IntPtr.Zero)
            Win32.CloseHandle(m_hProcess);
      }

      public ForeignWindow FirstChild
      {
         get
         {
            List<IntPtr> children = Win32.GetDescendentWindows(m_hwnd);
            return new ForeignWindow(children[0]);
         }
      }

      public bool Exists
      {
         get
         {
            return Win32.IsWindow(m_hwnd);
         }
      }

      public int ProcessId
      {
         get
         {
            if (m_ProcessId == 0)
               Win32.GetWindowThreadProcessId(m_hwnd, out m_ProcessId);
            return (int)m_ProcessId;
         }
      }

      public Process AssociatedProcess
      {
         get
         {
            try
            {
               return Process.GetProcessById(ProcessId);
            }
            catch
            {
               return null;
            }
         }
      }

      public UInt32 GetWindowLong(WindowLongId idx)
      {
         return Win32.GetWindowLong(m_hwnd, (int)idx);
      }

      public void SetWindowLong(WindowLongId idx, UInt32 value)
      {
         Win32.SetWindowLong(m_hwnd, (int)idx, value);
      }

      public ForeignWindow MainWindow
      {
         get
         {
            ForeignWindow fc = new ForeignWindow(m_hwnd);
            while (fc.Parent != null)
            {
               fc = fc.Parent;
            }
            return fc;
         }
      }

      public ForeignWindow Parent
      {
         get
         {
            IntPtr parentWnd = Win32.GetParent(m_hwnd);
            if (parentWnd == IntPtr.Zero) return null;
            return new ForeignWindow(parentWnd);
         }
      }
      public bool Visible
      {
         get
         {
            return Win32.IsWindowVisible(m_hwnd);
         }
      }
      public Rectangle ClientArea
      {
         get
         {
            RECT r = new RECT();
            Win32.GetClientRect(m_hwnd, out r);
            return r.ToRectangle();
         }
      }
      public Rectangle WindowRect
      {
         get
         {
            RECT r = new RECT();
            Win32.GetWindowRect(m_hwnd, out r);
            return r.ToRectangle();
         }
      }

      #endregion




      #region detailed info
      public UInt32 GetWindowLong(int idx)
      {
         return Win32.GetWindowLong(m_hwnd, idx);
      }

      public void SetWindowLong(int idx, UInt32 value)
      {
         Win32.SetWindowLong(m_hwnd, idx, value);
      }

      #endregion

      #region simulate keypress
      private uint GetScanCode(VirtualKeys key)
      {
         uint res = 0;
         switch (key)
         {
            case VirtualKeys.C:
               res = 0x2E;
               break;
            case VirtualKeys.Menu:
               res = 0x38;
               break;
            case VirtualKeys.Right:
               res = 0x4d;
               break;
            case VirtualKeys.Control:
               res = 0x1d;
               break;
         }
         return res;
      }

      public void SimulateKeyDown(VirtualKeys key)
      {
         uint scanCode = GetScanCode(key) << 16;
         if (key == VirtualKeys.Control)
            Win32.keybd_event((byte)key, 0, 0, 0);
         else if (key == VirtualKeys.Menu)
            Win32.keybd_event((byte)0x38, 0, 0, 0);
         Win32.PostMessage(m_hwnd, WindowsMessage.WM_KEYDOWN, (uint)key, AltCode + scanCode + 1);
      }

      public void SimulateKeyUp(VirtualKeys key)
      {
         uint scanCode = GetScanCode(key) << 16;
         Win32.PostMessage(m_hwnd, WindowsMessage.WM_KEYUP, (uint)key, AltCode + scanCode + 0xC0000001);
         if (key == VirtualKeys.Control)
            Win32.keybd_event((byte)key, 0, (uint)Win32.KEYEVENTF_KEYUP, 0);
         else if (key == VirtualKeys.Menu)
            Win32.keybd_event((byte)0x38, 0, (uint)Win32.KEYEVENTF_KEYUP, 0);
      }


      public void SimulateSysKeyDown(VirtualKeys key)
      {
         uint scanCode = GetScanCode(key) << 16;
         Win32.PostMessage(m_hwnd, WindowsMessage.WM_SYSKEYDOWN, (uint)key, AltCode + scanCode + 1);
      }

      public void SimulateSysKeyUp(VirtualKeys key)
      {
         uint scanCode = GetScanCode(key) << 16;
         Win32.PostMessage(m_hwnd, WindowsMessage.WM_SYSKEYUP, (uint)key, AltCode + scanCode + 0xC0000001);
      }

      public void SimulateKeyPress(VirtualKeys key, Keys modifiers = 0)
      {
         if ((modifiers & Keys.Control) != 0) SimulateKeyDown(VirtualKeys.Control);
         if ((modifiers & Keys.Shift) != 0) SimulateKeyDown(VirtualKeys.Shift);
         if ((modifiers & Keys.Alt) != 0) SimulateKeyDown(VirtualKeys.Menu);
         SimulateKeyDown(key);
         Thread.Sleep(10);
         SimulateKeyUp(key);
         if ((modifiers & Keys.Alt) != 0) SimulateKeyUp(VirtualKeys.Menu);
         if ((modifiers & Keys.Shift) != 0) SimulateKeyUp(VirtualKeys.Shift);
         if ((modifiers & Keys.Control) != 0) SimulateKeyUp(VirtualKeys.Control);
      }

      public void SimulateSysKeyPress(VirtualKeys key, Keys modifiers = 0)
      {
         if ((modifiers & Keys.Control) != 0) SimulateSysKeyDown(VirtualKeys.Control);
         if ((modifiers & Keys.Shift) != 0) SimulateSysKeyDown(VirtualKeys.Shift);
         if ((modifiers & Keys.Alt) != 0) SimulateSysKeyDown(VirtualKeys.Menu);
         SimulateSysKeyDown(key);
         SimulateSysKeyUp(key);
         if ((modifiers & Keys.Alt) != 0) SimulateSysKeyUp(VirtualKeys.Menu);
         if ((modifiers & Keys.Shift) != 0) SimulateSysKeyUp(VirtualKeys.Shift);
         if ((modifiers & Keys.Control) != 0) SimulateSysKeyUp(VirtualKeys.Control);
      }

      [Obsolete("Use SimulateKeyPress instead")]
      public void SimulateCtrlKey(VirtualKeys key)
      {
         SimulateKeyDown(VirtualKeys.Control);
         SimulateKeyPress(key);
         SimulateKeyUp(VirtualKeys.Control);
      }

      public void SimulateMouseDown(Point point, MouseButtons buttonPressed, MouseButtons buttonsDown, Keys modifiers)
      {
         WindowsMessage wm;
         switch (buttonPressed)
         {
            case MouseButtons.Left:
               wm = WindowsMessage.WM_LBUTTONDOWN;
               break;
            case MouseButtons.Middle:
               wm = WindowsMessage.WM_MBUTTONDOWN;
               break;
            case MouseButtons.Right:
               wm = WindowsMessage.WM_RBUTTONDOWN;
               break;
            default:
               throw new ArgumentException("Only Left, Middle or Right allowed", "buttonPressed");
         }

         Win32.PostMessage(m_hwnd, wm, GetMouseCode(buttonsDown, modifiers), (uint)(point.X + (point.Y << 16)));
      }

      public void SimulateMouseUp(Point point, MouseButtons buttonPressed, MouseButtons buttonsDown, Keys modifiers)
      {
         WindowsMessage wm;
         switch (buttonPressed)
         {
            case MouseButtons.Left:
               wm = WindowsMessage.WM_LBUTTONUP;
               break;
            case MouseButtons.Middle:
               wm = WindowsMessage.WM_MBUTTONUP;
               break;
            case MouseButtons.Right:
               wm = WindowsMessage.WM_RBUTTONUP;
               break;
            default:
               throw new ArgumentException("Only Left, Middle or Right allowed", "buttonPressed");
         }

         Win32.PostMessage(m_hwnd, wm, GetMouseCode(buttonsDown, modifiers), (uint)(point.X + (point.Y << 16)));
      }

      public void SimulateMouseClick(Point point, MouseButtons buttonPressed, MouseButtons buttonsDown, Keys modifiers)
      {
         SimulateMouseDown(point, buttonPressed, buttonsDown, modifiers);
         SimulateMouseUp(point, buttonPressed, buttonsDown, modifiers);
      }


      private uint GetMouseCode(MouseButtons button, Keys modifiers)
      {
         uint code = 0;
         if ((button & MouseButtons.Left) != 0) code |= 1;
         if ((button & MouseButtons.Right) != 0) code |= 2;
         if ((button & MouseButtons.Middle) != 0) code |= 0x10;
         if ((button & MouseButtons.XButton1) != 0) code |= 0x20;
         if ((button & MouseButtons.XButton2) != 0) code |= 0x40;
         if ((modifiers & Keys.Shift) != 0) code |= 4;
         if ((modifiers & Keys.Control) != 0) code |= 8;

         return code;
      }

      private uint AltCode
      {
         get
         {
            if (m_bAltDown)
               return 1 << 29;
            else
               return 0;
         }
      }

      public IEnumerable<ForeignWindow> Descendent
      {
         get
         {
            foreach (IntPtr child in Win32.GetDescendentWindows(m_hwnd))
            {
               yield return new ForeignWindow(child);
            }
         }
      }


      public IEnumerable<ForeignWindow> Children
      {
         get
         {
            foreach (IntPtr child in Win32.GetDescendentWindows(m_hwnd))
            {
               ForeignWindow wnd = new ForeignWindow(child);
               if (wnd.Parent == this)
                  yield return new ForeignWindow(child);
            }
         }
      }

      public static bool operator ==(ForeignWindow a, ForeignWindow b)
      {
         if ((object)a == null && (object)b == null) return true;
         if ((object)a == null || (object)b == null) return false;
         return a.m_hwnd == b.m_hwnd;
      }

      public static bool operator !=(ForeignWindow a, ForeignWindow b)
      {
         return !(a == b);
      }

      public override int GetHashCode()
      {
         return base.GetHashCode();
      }


      public override bool Equals(object obj)
      {
         return (obj is ForeignWindow) && (ForeignWindow)obj == this;
      }

      public void SimulateAltKey(VirtualKeys key)
      {
         SimulateSysKeyDown(VirtualKeys.Menu);
         SimulateSysKeyDown(key);
         SimulateKeyDown(key);
         SimulateKeyUp(key);
         SimulateSysKeyUp(key);
         SimulateSysKeyUp(VirtualKeys.Menu);
      }
      #endregion

      #region static
      public static ForeignWindow ForegroundWindow
      {
         get
         {
            return new ForeignWindow(Win32.GetForegroundWindow());
         }
      }

      public static IEnumerable<ForeignWindow> TopMostWindows
      {
         get
         {
            foreach (IntPtr topWindow in Win32.GetTopMostWindows())
            {
               yield return new ForeignWindow(topWindow);
            }
         }
      }

      public ForeignWindow GetPreviousWindow()
      {
         return new ForeignWindow(Win32.GetWindow(Handle, Win32.GetWindow_Cmd.GW_HWNDNEXT));
      }

      public bool IsOpenFileDialog()
      {
         if (ClassName != "#32770") return false;
         ForeignControl cmb = ForeignControl.GetControl(this, 0x470);
         ForeignControl btnOk = ForeignControl.GetControl(this, 0x1);
         ForeignControl btnCancel = ForeignControl.GetControl(this, 0x2);
         ForeignControl edit = ForeignControl.GetControl(this, 0x47c);
         return (cmb != null && btnOk != null && btnCancel != null && edit != null &&
            cmb.ClassName == "ComboBox" &&
            btnOk.ClassName == "Button" &&
            btnCancel.ClassName == "Button" &&
            edit.ClassName == "ComboBoxEx32");
      }

      public bool IsSaveFileDialog()
      {
         if (ClassName != "#32770") return false;
         ForeignControl btnOk = ForeignControl.GetControl(this, 0x1);
         ForeignControl btnCancel = ForeignControl.GetControl(this, 0x2);
         ForeignControl edit = ForeignControl.GetControlInAllDescendents(this, 0x3e9);
         return (btnOk != null && btnCancel != null && edit != null &&
            btnOk.ClassName == "Button" &&
            btnCancel.ClassName == "Button" &&
            edit.ClassName == "Edit");
      }


      public static ForeignWindow FindWindow(string caption, string className)
      {
         foreach (ForeignWindow wnd in TopMostWindows)
         {
            if ((wnd.Caption == caption || caption == null) &&
                (wnd.ClassName == className || className == null))
               return wnd;
         }
         return null;
      }
      /// <summary>
      /// Search a window
      /// </summary>
      /// <param name="caption"></param>
      /// <param name="className"></param>
      /// <param name="timeout">10 = 1 second</param>
      /// <returns></returns>
      public static ForeignWindow FindWindow(string caption, string className, int timeout)
      {
         return FindWindow(caption, className, timeout, false);
      }

      public static ForeignWindow FindWindow(string caption, string className, int timeout, bool bDoEvents)
      {
         while (timeout > 0)
         {
            foreach (ForeignWindow wnd in TopMostWindows)
            {
               if ((wnd.Caption == caption || caption == null) &&
                   (wnd.ClassName == className || className == null))
                  return wnd;
            }
            if (bDoEvents) Application.DoEvents();
            Thread.Sleep(100);
         }
         return null;
      }
      #endregion

      public ForeignWindow FindDescendent(string caption, string className, int index = 0)
      {
         return FindInGroup(Descendent, caption, className, index);
      }

      public ForeignWindow FindChild(string caption, string className, int index = 0)
      {
         return FindInGroup(Children, caption, className, index);
      }

      private static ForeignWindow FindInGroup(IEnumerable<ForeignWindow> group, string caption, string className, int index)
      {
         foreach (ForeignWindow wnd in group)
         {
            if ((wnd.Caption == caption || caption == null) &&
                (wnd.ClassName == className || className == null))
            {
               index--;
               if (index < 0) return wnd;
            }
         }
         return null;
      }

      public IEnumerable<ForeignWindow> WindowsInSameProcess()
      {
         List<ForeignWindow> windows = new List<ForeignWindow>();
         foreach (ProcessThread pt in AssociatedProcess.Threads)
         {
            Win32.EnumThreadWindows((uint)pt.Id, new Win32.EnumThreadDelegate(delegate(IntPtr hWnd, IntPtr lParam)
               {
                  ForeignWindow wnd = new ForeignWindow(hWnd);
                  if(wnd.Visible && wnd.PopUp && wnd.Overlapped)
                     windows.Add(wnd);
                  return true;
               }), IntPtr.Zero); 
         }
         return windows;
      }

      public uint ControlId
      {
         get
         {
            return GetWindowLong(WindowLongId.GWL_ID);
         }
      }


      public string ClassName
      {
         get
         {
            StringBuilder sb = new StringBuilder(256);
            Win32.GetClassName(m_hwnd, sb, 256);
            return sb.ToString();
         }
      }

      public string Caption
      {
         get
         {
            StringBuilder sb = new StringBuilder(256);
            Win32.GetWindowText(m_hwnd, sb, 256);
            return sb.ToString();
         }
      }

      #region SendMessages
      public void SendCommand(uint cmdId)
      {
         Win32.PostMessage(MainWindow.m_hwnd, WindowsMessage.WM_COMMAND, cmdId, 0);
      }

      public Rectangle ScreenRect
      {
         get
         {
            RECT r = new RECT();
            Win32.GetWindowRect(m_hwnd, out r);
            return r.ToRectangle();
         }
      }

      public ScrollInfo VScrollbarInfo
      {
         get
         {
            Win32.SCROLLINFO si = new Win32.SCROLLINFO();
            si.cbSize = (uint)Marshal.SizeOf(si);
            si.fMask = (uint)Win32.ScrollInfoMask.SIF_ALL;
            Win32.GetScrollInfo(m_hwnd, Win32.ScrollBarDirection.SB_VERT, ref si);
            ScrollInfo ret = new ScrollInfo(si);
            ret.Visible = (this.GetWindowLong(WindowLongId.GWL_STYLE) & WindowStyles.WS_VSCROLL) != 0;
            return ret;
         }
      }

      public bool Overlapped
      {
         get
         {
            return (this.GetWindowLong(WindowLongId.GWL_STYLE) & WindowStyles.WS_OVERLAPPEDWINDOW) != 0;
         }
      }

      public bool PopUp
      {
         get
         {
            return (this.GetWindowLong(WindowLongId.GWL_STYLE) & WindowStyles.WS_POPUPWINDOW) != 0;
         }
      }

      public bool Sizable
      {
         get
         {
            return (this.GetWindowLong(WindowLongId.GWL_STYLE) & WindowStyles.WS_THICKFRAME) != 0;
         }
      }

      public ScrollInfo HScrollbarInfo
      {
         get
         {
            Win32.SCROLLINFO si = new Win32.SCROLLINFO();
            si.cbSize = (uint)Marshal.SizeOf(si);
            si.fMask = (uint)Win32.ScrollInfoMask.SIF_ALL;
            Win32.GetScrollInfo(m_hwnd, Win32.ScrollBarDirection.SB_HORZ, ref si);
            ScrollInfo ret = new ScrollInfo(si);
            ret.Visible = (this.GetWindowLong(WindowLongId.GWL_STYLE) & WindowStyles.WS_VSCROLL) != 0;
            return ret;
         }
      }

      public void SendMessage(WindowsMessage msg, uint wParam, uint lParam)
      {
         Win32.SendMessage(m_hwnd, msg, wParam, lParam);
      }
      public uint SendMessage(WindowsMessage msg, uint wParam, StringBuilder lParam)
      {
         return Win32.SendMessage(m_hwnd, msg, wParam, lParam);
      }
      #endregion

      public Bitmap Snapshot(bool bScreenCopy = false)
      {
         if (bScreenCopy)
         {
            Size mySize = Size;
            if (mySize.Width > 0 && mySize.Height > 0)
            {
               //Bitmap me = new Bitmap(mySize.Width, mySize.Height);
               HBitmap bmp = null;
               using (DC dc = GetDC(), memDC = new DC(dc))
               {
                  try
                  {
                     bmp = new HBitmap(dc, mySize);
                     bmp.SelectInto(memDC);
                     Win32.PrintWindow(m_hwnd, memDC.Handle, 0);
                     //dc.CopyArea(new Rectangle(Point.Empty, mySize), memDC, Point.Empty);
                     bmp.ReleaseSelection();
                     Bitmap res = bmp.Bitmap;
                     bmp.Dispose();
                     return res;
                  }
                  catch
                  {
                  }
               }
            }
         }
         else
         {
            Bitmap res = new Bitmap(Width, Height);
            Graphics gr = Graphics.FromImage(res);
            gr.CopyFromScreen(ScreenRect.Location, Point.Empty, ScreenRect.Size);
         }
         return null;
      }

   }

   public class ScrollInfo
   {
      public int Maximum { get; set; }
      public int Minimum { get; set; }
      public int Position { get; set; }
      public bool Visible { get; set; }

      internal ScrollInfo(Win32.SCROLLINFO info)
      {
         Maximum = info.nMax;
         Minimum = info.nMin;
         Position = info.nPos;
      }
   }
}
